package isnork.g2;

import isnork.g2.InferenceBasedDangerAvoidance.QueueElement;
import isnork.sim.Observation;
import isnork.sim.SeaLifePrototype;
import isnork.sim.GameObject.Direction;

import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import javax.swing.text.html.HTMLDocument.Iterator;

public class InferenceBasedDangerAvoidance implements Strategy {

	@Override
	public Map<Direction, java.lang.Double> process(G2Explorer diver) {
		//System.out.printf("Danger avoidance process started\n");
		if (diver.obs_ext.isEmpty())
			return null;

		// classify dangerous creature into static or dynamic
		Set<Observation_Ext> dangerous_static = new HashSet<Observation_Ext>();
		Set<Observation_Ext> dangerous_dynamic = new HashSet<Observation_Ext>();
		for (Observation_Ext obs : diver.obs_ext) {
			if (obs.prototype.isDangerous()) {
				if (obs.prototype.getSpeed() == 0)
					dangerous_static.add(obs);
				else
					dangerous_dynamic.add(obs);
			}
		}

		if (dangerous_static.isEmpty() && dangerous_dynamic.isEmpty())
			return null;

		// compute dangerous map
		List<Map<Point2D, java.lang.Double>> Future = new ArrayList<Map<Point2D, java.lang.Double>>();
		for (int i = 1; i <= ExplorerConfig.T; ++i) {
			dangerous_dynamic = predict_next(dangerous_dynamic);
			Future.add(compute_danger_map(dangerous_dynamic, dangerous_static));
		}
		
		Map<Direction, java.lang.Double> utility_accumulator = attempt(diver.location, ExplorerConfig.T, Future);

		java.awt.geom.Point2D boat = new java.awt.geom.Point2D.Double(0,0);
		for (Direction dir:Direction.values())
		{
			Point2D dest = new Point2D.Double(diver.location.getX()+dir.getDx(), diver.location.getY()+dir.getDy());
			if(dest.equals(boat))
				utility_accumulator.put(dir, 0.0);
		}
		
		return utility_accumulator;
	}

	class QueueElement {
		public QueueElement(Point2D location, Direction direction, int ttl,
				double cost, Direction start_dir) {
			this.location = location;
			this.direction = direction;
			this.ttl = ttl;
			this.utility = cost;
			this.start_dir = start_dir;
		}

		Point2D location;
		Direction direction;
		int ttl;
		java.lang.Double utility;
		Direction start_dir;
	}

	private Map<Direction, java.lang.Double> attempt(Point2D location,
			int Time, List<Map<Point2D, java.lang.Double>> Future) {
		Map<Direction, java.lang.Double> utility_accumulators = new HashMap<Direction, java.lang.Double>();
		try {
			for (Direction dir : Direction.values())
				utility_accumulators.put(dir,
						java.lang.Double.NEGATIVE_INFINITY);

			Queue<QueueElement> queue = new LinkedList<QueueElement>();
			int time = 0;
			for (Direction dir : Direction.values()) {
				if (!dir.isDiag())
					queue.add(new QueueElement(location, dir, 2, 0.0, dir));
				else
					queue.add(new QueueElement(location, dir, 3, 0.0, dir));
			}

			Set<QueueElement> new_elements = new HashSet<QueueElement>();
			while (time < Time) {
				time++;
				new_elements.clear();
				while (queue.size() != 0) {
					QueueElement e = queue.remove();
					e.ttl--;
					if (e.ttl != 0) {
						java.lang.Double utility = Future.get(time - 1).get(
								e.location);
						if (utility != null)
							e.utility += utility;
						new_elements.add(new QueueElement(e.location, e.direction, e.ttl, e.utility, e.start_dir));
					} else if (e.ttl == 0) {
						e.location = new Point2D.Double(e.location.getX()
								+ e.direction.getDx(), e.location.getY()
								+ e.direction.getDy());
						java.lang.Double utility = Future.get(time - 1).get(
								e.location);
						if (utility != null)
							e.utility += utility;
						
						for (Direction dir : Direction.values()) {
							e.direction = dir;
							if (!dir.isDiag()) {
								e.ttl = 2;
								new_elements.add(new QueueElement(e.location, e.direction, e.ttl, e.utility, e.start_dir));
							} else {
								e.ttl = 3;
								new_elements.add(new QueueElement(e.location, e.direction, e.ttl, e.utility, e.start_dir));
							}
						}
					}
				}
				for (QueueElement e : new_elements)
					queue.add(e);
			}

			java.util.Iterator<QueueElement> iter = queue.iterator();
			while (iter.hasNext()) {
				QueueElement e = iter.next();
				java.lang.Double current = utility_accumulators
						.get(e.start_dir);
				if (e.utility > current)
					utility_accumulators.put(e.start_dir, e.utility);
			}

		} catch (Exception e) {
			e.printStackTrace(System.err);
		}
		
		for (Direction dir : Direction.values())
			utility_accumulators.put(dir, utility_accumulators.get(dir)*ExplorerConfig.D);
		return utility_accumulators;
	}

	private Map<Point2D, java.lang.Double> compute_danger_map(
			Set<Observation_Ext> dynamic_creatures,
			Set<Observation_Ext> static_creatures) {
		Map<Point2D, java.lang.Double> dmap = new HashMap<Point2D, java.lang.Double>();

		for (Observation_Ext obs : dynamic_creatures) {
			for (Direction dir : Direction.values()) {
				Point2D dest = new Point2D.Double(obs.location.getX() + dir.dx,
						obs.location.getY() + dir.dy);
				if (dmap.containsKey(dest)) {
					double cost = dmap.get(dest);
					dmap.put(dest, cost - 2 * obs.happiness);
				} else {
					dmap.put(dest, -2 * obs.happiness);
				}
			}
		}
		for (Observation_Ext obs : static_creatures) {
			for (Direction dir : Direction.values()) {
				Point2D dest = new Point2D.Double(obs.location.getX() + dir.dx,
						obs.location.getY() + dir.dy);
				if (dmap.containsKey(dest)) {
					double cost = dmap.get(dest);
					dmap.put(dest, cost - 2 * obs.happiness);
				} else {
					dmap.put(dest, -2 * obs.happiness);
				}
			}
		}

		return dmap;
	}

	private Set<Observation_Ext> predict_next(Set<Observation_Ext> observations) {
		Set<Observation_Ext> next = new HashSet<Observation_Ext>();
		for (Observation_Ext obs : observations) {
			if (obs.time == 1) { // ready to move non-diagonal
				for (Direction dir : Direction.values()) {
					if (!dir.isDiag()) {
						Point2D dest = new Point2D.Double(obs.location.getX()
								+ dir.dx, obs.location.getY() + dir.dy);
						if (dir == obs.dir)
							next.add(new Observation_Ext(obs.id, dest, dir, 0,
									obs.prototype, obs.happiness * 0.79));
						else
							next.add(new Observation_Ext(obs.id, dest, dir, 0,
									obs.prototype, obs.happiness * 0.21 / 8));
					}
				}
				next.add(new Observation_Ext(obs.id, obs.location, obs.dir, obs.time+1, obs.prototype, obs.happiness));
			} else if (obs.time == 2) { // ready to move diagonal
				for (Direction dir : Direction.values()) {
					if (dir.isDiag()) {
						Point2D dest = new Point2D.Double(obs.location.getX()
								+ dir.dx, obs.location.getY() + dir.dy);
						if (dir == obs.dir)
							next.add(new Observation_Ext(obs.id, dest, dir, 0,
									obs.prototype, obs.happiness * 0.79));
						else
							next.add(new Observation_Ext(obs.id, dest, dir, 0,
									obs.prototype, obs.happiness * 0.21 / 8));
					}
				}
			} else { // not ready to move
				next.add(new Observation_Ext(obs.id, obs.location, obs.dir,
						obs.time + 1, obs.prototype, obs.happiness));
			}
		}
		return next;
	}
}
